package com.jfm.jfmc.core.icc.policy;

import cn.hutool.core.util.StrUtil;
import com.jfm.jfmc.core.icc.entity.FrequencyEntity;
import com.jfm.jfmm.collector.entity.collect.CollectResult;
import com.jfm.jfmm.collector.entity.collect.Result;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.BitSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author DYang
 * @since 2024-10-18 22:54
 *
 * 采集降频策略，数据采集失败，或者延迟过高，降低现有采集策略：
 * 1、次数 < 5,疑似采集失败，暂时标记，若连续2次正常，则清除标记
 * 2、次数 >= 5 && 次数 < 10, 采集失败，降低采集频率，采集间隔 * 2 ，连续3次恢复，则降级到1级
 * 3、次数 >= 10 && 次数 < 20, 采集失败，降低采集频率，采集间隔 * 3 ,连续3次恢复，则降级到2级
 * 4、次数 >= 20 && 次数 < 50, 采集失败，降低采集频率，采集间隔 * 4 ，连续5次恢复，则降级到3级
 * 5、次数 >= 50, 采集失败，停止采集，需要人工手动处理，说明当前采集指标采集存在问题
 */
@Slf4j
public class ReduceFrequencyPolicy {

    /**
     * 等级阈值
     */
    private static final int LEVE1 = 5;
    private static final int LEVE2 = 10;
    private static final int LEVE3 = 20;
    private static final int LEVE4 = 50;

    /**
     * 记录错误等级
     */
    private static final Map<String, CountRecord> errRecordMap = new ConcurrentHashMap<>();

    /**
     * 采集策略降频
     * @param collectResult 采集结果
     * @return 降级策略
     */
    public static synchronized FrequencyEntity reducePolicy(CollectResult collectResult){
        Result result = collectResult.getResult();
        boolean error = isError(result);
        if (error) {
            // 标记采集失败
            addErrorPolicy(result.getMetricId(),result.getFrequency());
        }else {
            // 采集成功，降级策略
            removeErrorPolicy(result.getMetricId());
        }

        // 获取降级策略
        return getFrequency(result.getMetricId());
    }


    /**
     * 获取采集策略
     * @param metricId 任务采集器ID
     * @return
     */
    private static FrequencyEntity getFrequency(String metricId) {
        CountRecord record = errRecordMap.get(metricId);
        if (record == null) return null;

        int count = record.getCount().get();
        int frequency = record.getFrequency();
        if (count >= LEVE4) {
            return new FrequencyEntity(metricId, frequency,false);
        }else if (count >= LEVE3) {
            return new FrequencyEntity(metricId, frequency * 4);
        } else if (count >= LEVE2) {
            return new FrequencyEntity(metricId, frequency * 3);
        } else if (count >= LEVE1) {
            return new FrequencyEntity(metricId, frequency * 2);
        }else {
            return new FrequencyEntity(metricId,frequency);
        }
    }

    /**
     * 是否采集失败
     * @param result 采集结果
     * @return
     */
    private static boolean isError(Result result){
        String errorMsg = result.getErrorMsg();
        if (StrUtil.isNotEmpty(errorMsg) || result.getValue() == null) return true;

        long delay = result.getDelay();
        long timeout = result.getTimeout();
        return delay > timeout;
    }

    /**
     * 增加采集策略
     * @param metricId 任务采集器ID
     */
    private synchronized static void addErrorPolicy(String metricId,int frequency) {
        CountRecord record = errRecordMap.get(metricId);
        if (record == null) {
            record =  CountRecord.getInstance(frequency);
        }
        record.add(true);
        if (record.getCount().get() >= LEVE4) {
            // 设置最大存储数量，避免内存溢出和资源浪费
            return;
        }
        errRecordMap.put(metricId,record);
    }



    /**
     * 移除采集降级策略
     * @param metricId 任务采集器ID
     */
    private synchronized static void removeErrorPolicy(String metricId) {
        CountRecord record = errRecordMap.get(metricId);
        if (record == null) return;
        // 记录的数据全部移除，则全部移除
        if (record.getCount().get() <= 0) {
            errRecordMap.remove(metricId);
            return;
        }
        // 记录数据为false
        record.add(false);

        // 根据策略判断减少数据
        boolean continuous;
        int count = record.getCount().get();
        // 4级，数据连续5次都是正常,降到3级
        if (count >= LEVE3) {
            continuous = record.isContinuous(5);
            if (continuous) {
                record.initValue(LEVE3,true);
            }
        }else if (count >= LEVE2) {
            // 3级，数据连续3次都是正常，降到2级
            continuous = record.isContinuous(3);
            if (continuous) {
                record.initValue(LEVE2,true);
            }
        }else if (count >= LEVE1) {
            // 2级，数据连续2次都是正常，降到1级
            continuous = record.isContinuous(2);
            if (continuous) {
                record.initValue(LEVE1,true);
            }
        }else {
            // 1级，数据连续3次都是正常,删除
            continuous = record.isContinuous(3);
            if (continuous) {
                errRecordMap.remove(metricId);
            }
        }
    }


    /**
     * 数据记录
     */
    @Data
    private static class CountRecord{
        private AtomicInteger count;
        private BitSet bitSet;
        private int frequency;


        public CountRecord(int frequency) {
            this.count = new AtomicInteger();
            this.bitSet = new BitSet();
            this.frequency = frequency;
        }

        /**
         * 添加数据
         * @param flag
         */
        public void add(boolean flag){
            int i = count.incrementAndGet();
            bitSet.set(i - 1, flag);
        }

        /**
         * 初始化数据
         * @param value
         * @param flag
         */
        public void initValue(int value, boolean flag){
           count.set(value);
           bitSet.set(0, value, flag);
        }

        /**
         * 判断采集数据是否正常，正常bitset 中标记为false
         * @param setup 判断步长
         * @return true: 连续正常，false: 不连续
         */
        public boolean isContinuous(int setup){
            if (count.get() <= setup) return false;

            int res = 0;
            int i = count.get() - 1;
            while (i < count.get() - setup) {
                res += !bitSet.get(i) ? 1 : 0;
                if (res > 1) return false;
                i--;
            }
            return true;
        }

        /**
         * 构建对象
         * @return
         */
        public static CountRecord getInstance(int frequency){
            return new CountRecord(frequency);
        }

    }
}
